package com.gjm.tool.modifyLog.aspect;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.gjm.tool.modifyLog.annotaton.ModifyLog;
import com.gjm.tool.modifyLog.domain.ModifyHistoryLog;
import com.gjm.tool.modifyLog.service.IModifyHistoryLogService;
import com.gjm.tool.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletRequest;
import java.lang.reflect.Field;
import java.util.*;

@Slf4j
@Aspect
@Component
public class DataModifyLogAspect {
    /**
     * 排除敏感属性字段
     */
    public final List<String> EXCLUDE_PROPERTIES = Arrays.asList("serialVersionUID", "createBy", "createTime", "createUserId", "updateTime");
    /**
     * 数据库数据
     */
    private final ThreadLocal<Map<String, Object>> DATABASE_DATA = new NamedThreadLocal("databaseData");
    /**
     * 前端传参
     */
    private final ThreadLocal<String> WEB_TABLE_PRIMARY_ID_VALUE = new NamedThreadLocal("tablePrimaryId");
    @Autowired
    private IModifyHistoryLogService modifyHistoryLogService;

    /**
     * 处理请求前执行
     *
     * @param joinPoint 切点
     * @param modifyLog 注解对象
     */
    @Before(value = "@annotation(modifyLog)")
    public void boBefore(JoinPoint joinPoint, ModifyLog modifyLog) {
        try {
            //1. 获取前端传参
            String tablePrimaryIdName = toCamelCase(modifyLog.tablePrimaryIdName());
            RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
            Map<String, Object> paramMap = getParamMap(((ServletRequestAttributes) attributes).getRequest());
            if (StringUtils.isEmpty(paramMap)) {
                paramMap = argsArrayToMap(joinPoint.getArgs(), tablePrimaryIdName);
            }
            //2. 根据传参中的内容,获取数据id
            String tablePrimaryId = paramMap.get(tablePrimaryIdName) == null ? "" : paramMap.get(tablePrimaryIdName).toString();
            if (StrUtil.isBlank(tablePrimaryId)) {
                return;
            }
            //3. 根据传参,查询现有数据库内容,并把数据库内容保存到ThreadLocal中
            Map<String, Object> oldMap = getDatabaseMap(modifyLog.tableName(), modifyLog.tablePrimaryIdName(), tablePrimaryId, modifyLog.excludeFieldNames());
            DATABASE_DATA.remove();
            WEB_TABLE_PRIMARY_ID_VALUE.remove();
            DATABASE_DATA.set(oldMap);
            WEB_TABLE_PRIMARY_ID_VALUE.set(tablePrimaryId);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint  切点
     * @param modifyLog  注解对象
     * @param jsonResult 结果
     */
    @AfterReturning(pointcut = "@annotation(modifyLog)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, ModifyLog modifyLog, Object jsonResult) {
        recordLog(modifyLog);
    }

    /**
     * 记录日志
     *
     * @param modifyLog 注解对象
     */
    protected void recordLog(ModifyLog modifyLog) {
        try {
            //1. 获取前端传过来的主键id值
            String tablePrimaryIdName = toCamelCase(modifyLog.tablePrimaryIdName());
            String tablePrimaryIdValue = WEB_TABLE_PRIMARY_ID_VALUE.get();

            //2. 获取变更前数据库数据
            Map<String, Object> oldMap = DATABASE_DATA.get();
            if (StringUtils.isEmpty(oldMap)) {
                return;
            }
            //3. 获取变更后数据库数据
            Map<String, Object> newMap = getDatabaseMap(modifyLog.tableName(), modifyLog.tablePrimaryIdName(), tablePrimaryIdValue, modifyLog.excludeFieldNames());
            if (StringUtils.isEmpty(newMap)) {
                return;
            }

            //4. 对比内容,保存变更内容
            List<ModifyHistoryLog> logList = new ArrayList<>();
            for (Map.Entry<String, Object> entry : oldMap.entrySet()) {
                String fieldName = entry.getKey();
                if (tablePrimaryIdName.equals(fieldName)) {
                    continue;
                }
                String oldValue = entry.getValue() == null ? "" : oldMap.get(fieldName).toString();
                String newValue = newMap.get(fieldName) == null ? "" : newMap.get(fieldName).toString();
                if (!oldValue.equals(newValue)) {
                    ModifyHistoryLog historyLog = new ModifyHistoryLog();
                    historyLog.setTableName(modifyLog.tableName());
                    historyLog.setTablePrimaryId(tablePrimaryIdValue);
                    historyLog.setFieldName(fieldName);
                    historyLog.setOldValue(oldValue);
                    historyLog.setNewValue(newValue);
                    historyLog.setCreater("接入系统获取当前登录人的方法");
                    historyLog.setCreateTime(DateUtil.format(new Date(), DatePattern.NORM_DATETIME_PATTERN));
                    logList.add(historyLog);
                }
            }
            if (CollectionUtil.isNotEmpty(logList)) {
                modifyHistoryLogService.saveBatch(logList);
            }
        } catch (Exception exp) {
            // 记录本地异常日志
            log.error("异常信息:{}", exp.getMessage());
            exp.printStackTrace();
        } finally {
            //5. 清空缓存
            DATABASE_DATA.remove();
            WEB_TABLE_PRIMARY_ID_VALUE.remove();
        }
    }

    /**
     * 获取数据库内容map
     *
     * @param tableName           表名
     * @param tablePrimaryIdName  表主键id名称
     * @param tablePrimaryIdValue 表主键值
     * @param excludeFieldNames   过滤字段数组
     * @return 返回获取数据库内容map
     */
    public Map<String, Object> getDatabaseMap(String tableName, String tablePrimaryIdName, String tablePrimaryIdValue, String[] excludeFieldNames) {
        Map<String, Object> map = new HashMap<>();
        try {
            Map<String, Object> databaseMap = modifyHistoryLogService.getOldData(tableName, tablePrimaryIdName, tablePrimaryIdValue);
            if (databaseMap == null) {
                return map;
            }
            for (Map.Entry<String, Object> entry : databaseMap.entrySet()) {
                map.put(toCamelCase(entry.getKey()), entry.getValue());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return fieldFilter(map, excludeFieldNames);
    }

    /**
     * 获得所有请求参数
     *
     * @param request 请求对象{@link ServletRequest}
     * @return Map
     */
    public Map<String, Object> getParamMap(ServletRequest request) {
        Map<String, Object> params = new HashMap<>();
        final Map<String, String[]> map = request.getParameterMap();
        for (Map.Entry<String, String[]> entry : Collections.unmodifiableMap(map).entrySet()) {
            params.put(entry.getKey(), StringUtils.join(entry.getValue(), ","));
        }
        return params;
    }

    /**
     * 参数拼装
     */
    private Map<String, Object> argsArrayToMap(Object[] paramsArray, String tablePrimaryIdName) {
        Map<String, Object> paramsMap = new HashMap<>();
        if (paramsArray != null && paramsArray.length > 0) {
            for (Object o : paramsArray) {
                if (StringUtils.isNotNull(o)) {
                    if (o instanceof Integer || o instanceof Long || o instanceof String) {
                        paramsMap.put(tablePrimaryIdName, o.toString());
                    } else {
                        paramsMap = getModifiableFields(o);
                    }
                }
            }
        }
        return paramsMap;
    }

    /**
     * 获取所有需要修改的字段
     *
     * @param obj 对象
     * @return 获取所有需要修改的字段
     */
    public Map<String, Object> getModifiableFields(Object obj) {
        if (obj == null) {
            return null;
        }
        Map<String, Object> result = new HashMap<>();
        try {
            Field[] fields = obj.getClass().getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                // 字段值
                Object value = field.get(obj);
                if (value == null || StrUtil.isBlank(value.toString())) {
                    continue;
                }
                result.put(field.getName(), value);
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 驼峰式命名法 例如：user_name 变为 userName
     *
     * @param s 内容
     * @return 返回驼峰命名结果
     */
    public String toCamelCase(String s) {
        //下划线
        final char SEPARATOR = '_';
        if (s == null) {
            return null;
        }
        if (s.indexOf(SEPARATOR) == -1) {
            return s;
        }
        s = s.toLowerCase();
        StringBuilder sb = new StringBuilder(s.length());
        boolean upperCase = false;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);

            if (c == SEPARATOR) {
                upperCase = true;
            } else if (upperCase) {
                sb.append(Character.toUpperCase(c));
                upperCase = false;
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    /**
     * 字段过滤
     *
     * @param map               传入的map
     * @param excludeFieldNames 需要过滤的key
     * @return 过滤后的内容
     */
    public Map<String, Object> fieldFilter(Map<String, Object> map, String[] excludeFieldNames) {
        Map<String, Object> result = new HashMap<>();
        if (StringUtils.isEmpty(map)) {
            return result;
        }
        List<String> excludeFieldList = new ArrayList<>();
        excludeFieldList.addAll(Arrays.asList(excludeFieldNames));
        excludeFieldList.addAll(EXCLUDE_PROPERTIES);
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (excludeFieldList.contains(entry.getKey())) {
                continue;
            }
            result.put(entry.getKey(), entry.getValue());
        }
        return result;
    }
}